using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Cassandraemon.Operators;
using Cassandraemon.Util;
using Apache.Cassandra;


namespace Cassandraemon.Builder
{


	class CassandraDeletionBuilder : Cassandraemon.Util.ExpressionVisitor 
	{
		public byte[] Key { get; set; }
		public string ColumnFamily { get; set; }
		public Deletion Deletion { get; set; }
		

		public CassandraDeletionBuilder () 
		{ 
			Deletion = new Deletion();
		} 
		
		
		public void Build(Expression expression)
		{
			base.Visit(expression);
		}
		
		
		protected override Expression VisitLambda (LambdaExpression lambda)
		{
			ParseMethodCallArgumentExpression(lambda.Body);
			
			return base.VisitLambda (lambda);
		}

		
		private void ParseMethodCallArgumentExpression(Expression expression)
		{
			if(expression is BinaryExpression)
			{
				BinaryExpression b = (BinaryExpression)expression;
				switch (b.NodeType) 
				{
				case ExpressionType.Equal :
				case ExpressionType.GreaterThanOrEqual :
				case ExpressionType.LessThanOrEqual :
					GetConditionFromBinaryExpression(b);
					break;
				case ExpressionType.AndAlso :
					ParseMethodCallArgumentExpression(b.Left);
					ParseMethodCallArgumentExpression(b.Right);
					break;
				default:
					throw new InvalidOperationException("You can use Equal or AndAlso or GreaterThanOrEqual or LessThanEqual or Call in query condition.");
				}
			}
			else if(expression is MethodCallExpression)
			{
				GetConditionFromMethodCallExpression((MethodCallExpression)expression);
			}
			else
			{
				throw new InvalidOperationException("You can specify BinaryExpression or MethodCallExpression for query condition.");
			}
		}
		
		
		private void GetConditionFromBinaryExpression(BinaryExpression expression)
		{
			string memberName = string.Empty;
			object setValue = string.Empty;
			
			if(ExpressionChecker.IsCassandraEntityMemberAccess(expression.Left))
			{
				MemberExpression member = (MemberExpression)expression.Left;
				memberName = member.Member.Name;
				setValue = Expression.Lambda(expression.Right).Compile().DynamicInvoke();
			}
			else if(ExpressionChecker.IsCassandraEntityMemberAccess(expression.Right))
			{
				MemberExpression member = (MemberExpression)expression.Right;
				memberName = member.Member.Name;
				setValue = Expression.Lambda(expression.Left).Compile().DynamicInvoke();
			}
			
			if(expression.NodeType == ExpressionType.Equal)
			{
				switch (memberName.ToLowerInvariant()) 
				{
				case "key" :
					Key = setValue.ToCassandraByte();
					break;
				case "columnfamily" :
					ColumnFamily = setValue.ToString();
					break;
				case "supercolumn" :
					Deletion.Super_column = setValue.ToCassandraByte();
					break;
				case "column" :
					CreatePredicate();
					Deletion.Predicate.Column_names = new List<byte[]>{setValue.ToCassandraByte()};
					break;
				default:
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify Key, ColumnFamily, SuperColumn, Columnin Equal expression.");
				}
			}
			else if(expression.NodeType == ExpressionType.GreaterThanOrEqual)
			{
				if(memberName == "Column" || memberName == "SuperColumn")
				{
					CreateSliceRange();
					Deletion.Predicate.Slice_range.Start = setValue.ToCassandraByte();
				}
				else
				{
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify SuperColumn, Column in GreaterThanOrEqual expression.");
				}
			}
			else if(expression.NodeType == ExpressionType.LessThanOrEqual)
			{
				if(memberName == "Column" || memberName == "SuperColumn")
				{
					CreateSliceRange();
					Deletion.Predicate.Slice_range.Finish = setValue.ToCassandraByte();
				}
				else
				{
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify SuperColumn, Column in LessThanOrEqual expression.");
				}
			}
			else
			{
				throw new InvalidOperationException("You can use Equal only in query condition operator.");
			}
		}
		
		
		private void GetConditionFromMethodCallExpression(MethodCallExpression expression)
		{
			var memberName = ((MemberExpression)expression.Arguments[0]).Member.Name;
			
			if(expression.Method.Name == "In")
			{
				if(expression.Arguments.Count() > 1)
				{
					if(memberName == "Column" || memberName == "SuperColumn")
					{
						CreatePredicate();
						if(Deletion.Predicate.Slice_range != null)
						{
							throw new InvalidOperationException("You should set which ColumnList or SliceRange.");
						}						
						Array value = (Array)Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
						Deletion.Predicate.Column_names = BuilderUtil.ConvertArray(value, x => x.ToCassandraByte());
					}
					else
					{
						throw new InvalidOperationException("You can use 'In' method belong [SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify more than one arguments for 'In' method.");
				}
			}
			else if(expression.Method.Name == "GreaterThanOrEqual")
			{
				if(expression.Arguments.Count() == 2)
				{
					if(memberName == "Column" || memberName == "SuperColumn")
					{
						CreateSliceRange();
						object value = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
						Deletion.Predicate.Slice_range.Start = value.ToCassandraByte();
					}
					else
					{
						throw new InvalidOperationException("You can use 'GreaterThanOrEqual' method belong [SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify one argument for 'GreaterThanOrEqual' method.");
				}
			}
			else if(expression.Method.Name == "LessThanOrEqual")
			{
				if(expression.Arguments.Count() == 2)
				{
					if(memberName == "Column" || memberName == "SuperColumn")
					{
						CreateSliceRange();
						object value = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
						Deletion.Predicate.Slice_range.Finish = value.ToCassandraByte();
					}
					else
					{
						throw new InvalidOperationException("You can use 'LessThanOrEqual' method belong [SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify one argument for 'LessThanOrEqual' method.");
				}
			}
			else if(expression.Method.Name == "Between")
			{
				if(expression.Arguments.Count() == 3)
				{
					if(memberName == "Column" || memberName == "SuperColumn")
					{
						CreateSliceRange();
						object value1 = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
						object value2 = Expression.Lambda(expression.Arguments[2]).Compile().DynamicInvoke();
						byte[] column1 = value1.ToCassandraByte();
						byte[] column2 = value2.ToCassandraByte();
						Deletion.Predicate.Slice_range.Start = column1;
						Deletion.Predicate.Slice_range.Finish = column2;
					}
					else
					{
						throw new InvalidOperationException("You can use 'Between' method belong [SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify two argument for 'Between' method.");
				}
			}
			else
			{
				throw new InvalidOperationException("You can use 'In, GreaterThanOrEqual, LessThanOrEqual, Between' method in query condition.");
			}
		}
		
		
		private void CreatePredicate()
		{
			if(Deletion.Predicate == null)
			{
				Deletion.Predicate = new SlicePredicate();
			}
		}
		
		private void CreateSliceRange()
		{
			if(Deletion.Predicate == null)
			{
				Deletion.Predicate = new SlicePredicate();
			}
			
			if(Deletion.Predicate.Column_names != null)
			{
				throw new InvalidOperationException("You should set which ColumnList or SliceRange.");
			}
		
			if(Deletion.Predicate.Slice_range == null)
			{
				Deletion.Predicate.Slice_range = new SliceRange();
			}
		}
	}
}
